#include <iostream>

#include "srs_kernel_error.hpp"
#include "srs_kernel_log.hpp"
#include "srs_app_log.hpp"
#include "srs_service_log.hpp"
#include <srs_app_config.hpp>
#include <sstream>
#include <unistd.h>
using namespace std;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "st.h"

// @global log and context.
ISrsLog* _srs_log = new SrsFastLog();
ISrsThreadContext* _srs_context = new SrsThreadContext();
SrsConfig* _srs_config = new SrsConfig();


#define IOBUFSIZE (16*1024)

#define IOV_LEN   256
#define IOV_COUNT (IOBUFSIZE / IOV_LEN)

#ifndef INADDR_NONE
#define INADDR_NONE 0xffffffff
#endif

static char *prog;                     /* Program name   */
static struct sockaddr_in rmt_addr;    /* Remote address */

static unsigned long testing;
#define TESTING_VERBOSE		0x1
#define TESTING_READV		0x2
#define	TESTING_READ_RESID	0x4
#define TESTING_WRITEV		0x8
#define TESTING_WRITE_RESID	0x10

static void read_address(const char *str, struct sockaddr_in *sin);
static void start_daemon(void);
static int  cpu_count(void);
static void set_concurrency(int nproc);
static void *handle_request(void *arg);
static void print_sys_error(const char *msg);


/*
 * This program acts as a generic gateway. It listens for connections
 * to a local address ('-l' option). Upon accepting a client connection,
 * it connects to the specified remote address ('-r' option) and then
 * just pumps the data through without any modification.
 */
int main(int argc, char *argv[])
{
    extern char *optarg;
    int opt, sock, n;
    int laddr, raddr, num_procs, alt_ev, one_process;
    int serialize_accept = 0;
    struct sockaddr_in lcl_addr, cli_addr;
    st_netfd_t cli_nfd, srv_nfd;

    prog = argv[0];
    num_procs = laddr = raddr = alt_ev = one_process = 0;

    laddr = 1;
    raddr =1;

    if (!laddr) {
        fprintf(stderr, "%s: local address required\n", prog);
        exit(1);
    }
    if (!raddr) {
        fprintf(stderr, "%s: remote address required\n", prog);
        exit(1);
    }

    fprintf(stderr, "%s: starting proxy daemon on %s:%d\n", prog,
            inet_ntoa(lcl_addr.sin_addr), ntohs(lcl_addr.sin_port));

    /* Initialize the ST library */
    if (st_init() < 0) {
        print_sys_error("st_init");
        exit(1);
    }

    /* Create and bind listening socket */
    if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
        print_sys_error("socket");
        exit(1);
    }
    n = 1;
    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&n, sizeof(n)) < 0) {
        print_sys_error("setsockopt");
        exit(1);
    }

    read_address("192.168.31.199:5060",&lcl_addr);
    read_address("192.168.31.62:8080",&rmt_addr);

    if (bind(sock, (struct sockaddr *)&lcl_addr, sizeof(lcl_addr)) < 0) {
        print_sys_error("bind");
        exit(1);
    }
    listen(sock, 128);
    if ((srv_nfd = st_netfd_open_socket(sock)) == NULL) {
        print_sys_error("st_netfd_open");
        exit(1);
    }


    for ( ; ; ) {
        n = sizeof(cli_addr);
        cli_nfd = st_accept(srv_nfd, (struct sockaddr *)&cli_addr, &n,
                            ST_UTIME_NO_TIMEOUT);
        if (cli_nfd == NULL) {
            print_sys_error("st_accept");
            exit(1);
        }
        if (st_thread_create(handle_request, cli_nfd, 0, 0) == NULL) {
            print_sys_error("st_thread_create");
            exit(1);
        }
    }

    /* NOTREACHED */
    return 1;
}


static void read_address(const char *str, struct sockaddr_in *sin)
{
    char host[128], *p;
    struct hostent *hp;
    unsigned short port;

    strcpy(host, str);
    if ((p = strchr(host, ':')) == NULL) {
        fprintf(stderr, "%s: invalid address: %s\n", prog, host);
        exit(1);
    }
    *p++ = '\0';
    port = (unsigned short) atoi(p);
    if (port < 1) {
        fprintf(stderr, "%s: invalid port: %s\n", prog, p);
        exit(1);
    }

    memset(sin, 0, sizeof(struct sockaddr_in));
    sin->sin_family = AF_INET;
    sin->sin_port = htons(port);
    if (host[0] == '\0') {
        sin->sin_addr.s_addr = INADDR_ANY;
        return;
    }
    sin->sin_addr.s_addr = inet_addr(host);
    if (sin->sin_addr.s_addr == INADDR_NONE) {
        /* not dotted-decimal */
        if ((hp = gethostbyname(host)) == NULL) {
            fprintf(stderr, "%s: can't resolve address: %s\n", prog, host);
            exit(1);
        }
        memcpy(&sin->sin_addr, hp->h_addr, hp->h_length);
    }
}

#ifdef DEBUG
static void show_iov(const struct iovec *iov, int niov)
{
  int i;
  size_t total;

  printf("iov %p has %d entries:\n", iov, niov);
  total = 0;
  for (i = 0; i < niov; i++) {
    printf("iov[%3d] iov_base=%p iov_len=0x%lx(%lu)\n",
     i, iov[i].iov_base, (unsigned long) iov[i].iov_len,
     (unsigned long) iov[i].iov_len);
    total += iov[i].iov_len;
  }
  printf("total 0x%lx(%ld)\n", (unsigned long) total, (unsigned long) total);
}

/*
 * This version is tricked out to test all the
 * st_(read|write)v?(_resid)? variants.  Use the non-DEBUG version for
 * anything serious.  st_(read|write) are all this function really
 * needs.
 */
static int pass(st_netfd_t in, st_netfd_t out)
{
  char buf[IOBUFSIZE];
  struct iovec iov[IOV_COUNT];
  int ioviter, nw, nr;

  if (testing & TESTING_READV) {
    for (ioviter = 0; ioviter < IOV_COUNT; ioviter++) {
      iov[ioviter].iov_base = &buf[ioviter * IOV_LEN];
      iov[ioviter].iov_len = IOV_LEN;
    }
    if (testing & TESTING_VERBOSE) {
      printf("readv(%p)...\n", in);
      show_iov(iov, IOV_COUNT);
    }
    if (testing & TESTING_READ_RESID) {
      struct iovec *riov = iov;
      int riov_cnt = IOV_COUNT;
      if (st_readv_resid(in, &riov, &riov_cnt, ST_UTIME_NO_TIMEOUT) == 0) {
	if (testing & TESTING_VERBOSE) {
	  printf("resid\n");
	  show_iov(riov, riov_cnt);
	  printf("full\n");
	  show_iov(iov, IOV_COUNT);
	}
	nr = 0;
	for (ioviter = 0; ioviter < IOV_COUNT; ioviter++)
	  nr += iov[ioviter].iov_len;
	nr = IOBUFSIZE - nr;
      } else
	nr = -1;
    } else
      nr = (int) st_readv(in, iov, IOV_COUNT, ST_UTIME_NO_TIMEOUT);
  } else {
    if (testing & TESTING_READ_RESID) {
      size_t resid = IOBUFSIZE;
      if (st_read_resid(in, buf, &resid, ST_UTIME_NO_TIMEOUT) == 0)
	nr = IOBUFSIZE - resid;
      else
	nr = -1;
    } else
      nr = (int) st_read(in, buf, IOBUFSIZE, ST_UTIME_NO_TIMEOUT);
  }
  if (testing & TESTING_VERBOSE)
    printf("got 0x%x(%d) E=%d\n", nr, nr, errno);

  if (nr <= 0)
    return 0;

  if (testing & TESTING_WRITEV) {
    for (nw = 0, ioviter = 0; nw < nr;
     nw += iov[ioviter].iov_len, ioviter++) {
      iov[ioviter].iov_base = &buf[nw];
      iov[ioviter].iov_len = nr - nw;
      if (iov[ioviter].iov_len > IOV_LEN)
	iov[ioviter].iov_len = IOV_LEN;
    }
    if (testing & TESTING_VERBOSE) {
      printf("writev(%p)...\n", out);
      show_iov(iov, ioviter);
    }
    if (testing & TESTING_WRITE_RESID) {
      struct iovec *riov = iov;
      int riov_cnt = ioviter;
      if (st_writev_resid(out, &riov, &riov_cnt, ST_UTIME_NO_TIMEOUT) == 0) {
	if (testing & TESTING_VERBOSE) {
	  printf("resid\n");
	  show_iov(riov, riov_cnt);
	  printf("full\n");
	  show_iov(iov, ioviter);
	}
	nw = 0;
	while (--ioviter >= 0)
	  nw += iov[ioviter].iov_len;
	nw = nr - nw;
      } else
	nw = -1;
    } else
      nw = st_writev(out, iov, ioviter, ST_UTIME_NO_TIMEOUT);
  } else {
    if (testing & TESTING_WRITE_RESID) {
      size_t resid = nr;
      if (st_write_resid(out, buf, &resid, ST_UTIME_NO_TIMEOUT) == 0)
	nw = nr - resid;
      else
	nw = -1;
    } else
      nw = st_write(out, buf, nr, ST_UTIME_NO_TIMEOUT);
  }
  if (testing & TESTING_VERBOSE)
    printf("put 0x%x(%d) E=%d\n", nw, nw, errno);

  if (nw != nr)
    return 0;

  return 1;
}
#else /* DEBUG */
/*
 * This version is the simple one suitable for serious use.
 */
static int pass(st_netfd_t in, st_netfd_t out)
{
    char buf[IOBUFSIZE];
    int nw, nr;

    nr = (int) st_read(in, buf, IOBUFSIZE, ST_UTIME_NO_TIMEOUT);
    if (nr <= 0)
        return 0;

    nw = st_write(out, buf, nr, ST_UTIME_NO_TIMEOUT);
    if (nw != nr)
        return 0;

    return 1;
}
#endif

static void *handle_request(void *arg)
{
    struct pollfd pds[2];
    st_netfd_t cli_nfd, rmt_nfd;
    int sock;

    cli_nfd = (st_netfd_t) arg;
    pds[0].fd = st_netfd_fileno(cli_nfd);
    pds[0].events = POLLIN;

    /* Connect to remote host */
    if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
        print_sys_error("socket");
        goto done;
    }
    if ((rmt_nfd = st_netfd_open_socket(sock)) == NULL) {
        print_sys_error("st_netfd_open_socket");
        close(sock);
        goto done;
    }
    if (st_connect(rmt_nfd, (struct sockaddr *)&rmt_addr,
                   sizeof(rmt_addr), ST_UTIME_NO_TIMEOUT) < 0) {
        print_sys_error("st_connect");
        st_netfd_close(rmt_nfd);
        goto done;
    }
    pds[1].fd = sock;
    pds[1].events = POLLIN;

    /*
     * Now just pump the data through.
     * XXX This should use one thread for each direction for true full-duplex.
     */
    for ( ; ; ) {
        pds[0].revents = 0;
        pds[1].revents = 0;

        if (st_poll(pds, 2, ST_UTIME_NO_TIMEOUT) <= 0) {
            print_sys_error("st_poll");
            break;
        }

        if (pds[0].revents & POLLIN) {
            if (!pass(cli_nfd, rmt_nfd))
                break;
        }

        if (pds[1].revents & POLLIN) {
            if (!pass(rmt_nfd, cli_nfd))
                break;
        }
    }
    st_netfd_close(rmt_nfd);

    done:

    st_netfd_close(cli_nfd);

    return NULL;
}

static void start_daemon(void)
{
    pid_t pid;

    /* Start forking */
    if ((pid = fork()) < 0) {
        print_sys_error("fork");
        exit(1);
    }
    if (pid > 0)
        exit(0);                        /* parent */

    /* First child process */
    setsid();                         /* become session leader */

    if ((pid = fork()) < 0) {
        print_sys_error("fork");
        exit(1);
    }
    if (pid > 0)                      /* first child */
        exit(0);

    chdir("/");
    umask(022);
}

/*
 * Create separate processes ("virtual processors"). Since it's just an
 * example, there is no watchdog - the parent just exits leaving children
 * on their own.
 */
static void set_concurrency(int nproc)
{
    pid_t pid;
    int i;

    if (nproc < 1)
        nproc = 1;

    for (i = 0; i < nproc; i++) {
        if ((pid = fork()) < 0) {
            print_sys_error("fork");
            exit(1);
        }
        /* Child returns */
        if (pid == 0)
            return;
    }

    /* Parent just exits */
    exit(0);
}

static int cpu_count(void)
{
    int n;

#if defined (_SC_NPROCESSORS_ONLN)
    n = (int) sysconf(_SC_NPROCESSORS_ONLN);
#elif defined (_SC_NPROC_ONLN)
    n = (int) sysconf(_SC_NPROC_ONLN);
#elif defined (HPUX)
#include <sys/mpctl.h>
  n = mpctl(MPC_GETNUMSPUS, 0, 0);
#else
  n = -1;
  errno = ENOSYS;
#endif

    return n;
}

static void print_sys_error(const char *msg)
{
    fprintf(stderr, "%s: %s: %s\n", prog, msg, strerror(errno));
}